/**
 * 19.3.4：增加颜色渐变逻辑。
 * 19.3.6：修正vertexShader中sizeAttenuation写反的错误
 * 19.3.11：endInfo重置starts数据，修正停止后重新播放时gradual失效的bug
 * 19.3.21:修改速度增量计算方式（按50帧计算增量），避免因卡顿造成粒子散开
 * 19.4.8:增加rangeBound粒子范围检测
 * 19.4.8:修正dFrame一直归零导致gradual递增中断的bug
 * 19.4.8:增加offset属性控制激活的粒子数量
 * 19.4.19:增加scale属性相关代码解决窗口resize时的诡异现象
 * 20.10.29:移动到新库，去除size的Pr矫正
 */

let THREE=require('three');
import Props from '../general/props';
import Utils from '../general/utils';

const FRACTION=(num,mul=1)=>{
    let n=num*mul;
    return n-Math.floor(n);
};

/**
 * multi texture split area(maximum 6):
 *  ______     _____________    _____________    _____________    ____________________    ____________________
 * | tex1 |   | tex1 | tex2 |  | tex1 | tex2 |  | tex1 | tex2 |  | tex1 | tex2 | tex3 |  | tex1 | tex2 | tex3 |
 * |______|   |______|______|  |______|______|  |______|______|  |______|______|______|  |______|______|______|
 *                             |     tex3    |  | tex3 | tex4 |  |   tex4   |  tex5   |  | tex4 | tex5 | tex6 |
 *                             |_____________|  |______|______|  |__________|_________|  |______|______|______|
*/
export default class Particle extends THREE.Object3D{
    constructor(values={}){
        super();
        this.props=new Props({
            splitCount:values.splitCount||1,//..........................................贴图分割数量，最大6
            gradual:values.gradual||false,//...........................................粒子增加是否渐进
            gradualCount:values.gradualCount||2,//......................................粒子增加渐进量
            gradualDelta:values.gradualDelta||1,//......................................渐进增加时间间隔
            force:values.force||new THREE.Vector3(0.02,0,0),//..........................粒子所受力
            color:values.color||new THREE.Color(0.3,0.1,0.1),//.........................修正颜色
            texture:values.texture||null,//.............................................贴图
            count:values.count||100,//...................................................粒子数量
            startSize:values.startSize||0.3,//...........................................起始尺寸
            lifeMin:values.lifeMin||0,//.................................................随机生命周期小值
            lifeMax:values.lifeMax||2,//.................................................随机生命周期大值
            speedMin:values.speedMin||1,//...............................................随机速度小值
            speedMax:values.speedMax||2,//...............................................随机速度大值
            alphaMin:values.alphaMin||0.5,//.............................................随机透明度修正小值
            alphaMax:values.alphaMax||1,//...............................................随机透明度修正大值
            sizeMin:values.sizeMin||0.1,//...............................................随机尺寸小值
            sizeMax:values.sizeMax||2,//........................................ ........随机尺寸大值
            rotMin:values.rotMin||0,//...................................................随机旋转小值
            rotMax:values.rotMax||6.24,//................................................随机旋转大值
            dLife:values.dLife||0.01,//..................................................生命周期递减值
            dAlpha:values.dAlpha!==undefined?values.dAlpha:-0.005,//.....................透明度递增值
            dSize:values.dSize!==undefined?values.dSize:-0.001,//........................尺寸递增值
            dRot:values.dRot!==undefined?values.dRot:0.05,//.............................旋转递增/减值
            dColor:values.dColor!==undefined?values.dColor:new THREE.Vector3(),//........颜色增量
            sizeAttenuation:values.sizeAttenuation||false,//............................保持视觉大小
            offset:values.offset||0,//...................................................激活粒子数量的偏移值
            scale:values.scale||1024,//..........................................缩放倍率
        });
        this.geometry=new THREE.BufferGeometry();
        this.material=new THREE.ShaderMaterial();
    }
    get status(){
        return this.state;
    }
    awake(){
            this.init();
        };
    play(){
            cancelAnimationFrame(this.requests[1]);
            if(this.state===0)this.endInfo();
            this.state=1;
            let animate=()=>{
                if(this.oldTime===null){
                    this.oldTime=performance.now();
                    this.dTime=0;
                    this.dFrame=0;
                }else{
                    let now=performance.now();
                    this.dTime=(now-this.oldTime)*0.001;
                    this.dFrame%=this.props.gradualDelta.value;//19.4.8
                    this.dFrame++;
                    this.oldTime=now;
                }
                this.computeVerts();
                if(this.state!==1){
                    this.oldTime=null;
                }else {
                    this.requests[0] = requestAnimationFrame(animate);
                }
            };
            animate();
        };
    pause(){
            this.state=2;
        }
    stop(immediately=false){
            this.state=0;
            cancelAnimationFrame(this.requests[0]);
            if(immediately) {
                this.endInfo();
            }else{
                let animate=()=> {
                    let allOver = true;
                    for (let i = 0, t = 0; i < this.verts.length; i += 3, t++) {
                        if (this.verts[i] !== 0 || this.verts[i + 1] !== 0 || this.verts[i + 2] !== 0) {
                            allOver = false;
                            this.updateInfo(i,t);
                        }
                    }
                    this.updateAttribute();
                    if(allOver){
                        cancelAnimationFrame(this.requests[1]);
                        this.endInfo();
                    }else{
                        this.requests[1]=requestAnimationFrame(animate);
                    }
                };
                animate();
            }
        }
    set dynamicForce(func_paras_ax_ay_az_px_py_pz_dt){
        this.dynamicField=(i,t)=>{
            let dn=func_paras_ax_ay_az_px_py_pz_dt(
                this.accelerates[i],this.accelerates[i+1],this.accelerates[i+2],this.verts[i],this.verts[i+1],this.verts[i+2],this.dTime
            );
            if(!dn){
                this.dynamics[i]=0;
                this.dynamics[i+1]=0;
                this.dynamics[i+2]=0;
                return;
            }
            this.dynamics[i]=dn[0];
            this.dynamics[i+1]=dn[1];
            this.dynamics[i+2]=dn[2];
        };
    }
    set rangeBound(func_x_y_z_r_bool){
        this.range=func_x_y_z_r_bool;
    }
    init() {
        this.initVariable();
        this.initComponents();
        this.initPropCallback();
    }
    initPropCallback(){
        this.props.onKeyChange('splitCount',()=>{
            this.material.uniforms.splitCount.value=this.props.splitCount.value;
        });
        this.props.onKeyChange('count',()=>{
            let c3=this.props.count.value*3;
            let c1=this.props.count.value;
            this.verts=Utils.resizeTypedArray(this.verts,c3);
            this.lifes=Utils.resizeTypedArray(this.lifes,c1);
            this.speeds=Utils.resizeTypedArray(this.speeds,c3);
            this.accelerates=Utils.resizeTypedArray(this.accelerates,c3);
            this.dynamics=Utils.resizeTypedArray(this.dynamics,c3);
            this.alphas=Utils.resizeTypedArray(this.alphas,c1);
            this.colors=Utils.resizeTypedArray(this.colors,c3);
            this.sizes=Utils.resizeTypedArray(this.sizes,c1);
            this.starts=Utils.resizeTypedArray(this.starts,c1);
            this.rots=Utils.resizeTypedArray(this.rots,c1);
            this.splits=Utils.resizeTypedArray(this.splits,c1);
            this.geometry.attributes.position=new THREE.BufferAttribute(this.verts,3);
            this.geometry.attributes.alpha=new THREE.BufferAttribute(this.alphas,1);
            this.geometry.attributes.color=new THREE.BufferAttribute(this.colors,3);
            this.geometry.attributes.size=new THREE.BufferAttribute(this.sizes,1);
            this.geometry.attributes.rot=new THREE.BufferAttribute(this.rots,1);
            this.geometry.attributes.split=new THREE.BufferAttribute(this.splits,1);
        });
        this.props.onKeyChange('texture',()=>{
            this.material.uniforms.map.value=this.props.texture.value;
            this.material.fragmentShader=this.props.texture.value?this.fragmentShaderSampler:this.fragmentShaderColour;
            this.material.needsUpdate=true;
        });
        this.props.onKeyChange('scale',()=>{
            this.material.uniforms.scale.value=this.props.scale.value;
        });
    }
    computeVerts(){
        this.tempCount=0;
        for(let i=0,t=0;i<this.verts.length;i+=3,t++){
            if((this.verts[i]===0)&&
                (this.verts[i+1]===0)&&
                (this.verts[i+2]===0)&&
                (t<this.props.count.value-this.props.offset.value)){//19.4.8
                let rand=Math.random();
                if(!this.checkGradual(t))continue;
                this.setStartInfo(rand,i,t);
            }else{
                this.updateInfo(i,t);
            }
        }
        this.updateAttribute();
    }
    setStartInfo(rand,i,t){
        this.lifes[t]=this.props.lifeMin.value+(this.props.lifeMax.value-this.props.lifeMin.value)*rand;
        this.alphas[t]=this.props.alphaMin.value+(this.props.alphaMax.value-this.props.alphaMin.value)*FRACTION(rand,1e1);
        this.colors[i]=this.props.color.value.r;
        this.colors[i+1]=this.props.color.value.g;
        this.colors[i+2]=this.props.color.value.b;
        this.sizes[t]=this.props.sizeMin.value+(this.props.sizeMax.value-this.props.sizeMin.value)*FRACTION(rand,1e2);
        this.rots[t]=this.props.rotMin.value+(this.props.rotMax.value-this.props.rotMin.value)*FRACTION(rand,1e3);
        this.splits[t]=Utils.clamp(Math.floor(rand*this.props.splitCount.value),0,this.props.splitCount.value-1);
        this.accelerates[i]=this.props.force.value.x;
        this.accelerates[i+1]=this.props.force.value.y;
        this.accelerates[i+2]=this.props.force.value.z;
        this.setStartVertSpeed(rand,i,t);
    }
    setStartVertSpeed(rand,i,t){
        this.verts[i]=-this.props.startSize.value+this.props.startSize.value*2*FRACTION(rand,1e4);
        this.verts[i+2]=-this.props.startSize.value+this.props.startSize.value*2*FRACTION(rand,1e5);
        this.speeds[i]=0;
        this.speeds[i+1]=this.props.speedMin.value+(this.props.speedMax.value-this.props.speedMin.value)*FRACTION(rand,1e6);
        this.speeds[i+2]=0;
    }
    checkGradual(t){
        if(!this.props.gradual.value)return true;
        if(this.dFrame<this.props.gradualDelta.value)return false;
        // this.dFrame=0;//19.4.8
        if(this.starts[t]===0){
            if(this.tempCount>this.props.gradualCount.value){
                return false;
            }else{
                this.starts[t]=1;
                this.tempCount++;
                return true;
            }
        }
        return true;
    }
    endInfo(){
        for (let i = 0, t = 0; i < this.verts.length; i += 3, t++) {
            this.verts[i] = 0;
            this.verts[i + 1] = 0;
            this.verts[i + 2] = 0;
            this.colors[i]=0;
            this.colors[i+1]=0;
            this.colors[i+2]=0;
            this.alphas[t] = 0;
            this.sizes[t] = 0;
            this.rots[t]=0;
            this.splits[t]=0;
            this.starts[t]=0;//19-3-11
        }
        this.oldTime=null;
        this.updateAttribute();
    }
    updateInfo(i,t){
        this.lifes[t]-=this.props.dLife.value;
        if((this.lifes[t]<=0)||(t>=this.props.count.value-this.props.offset.value)){//19.4.8
            this.verts[i]=0;
            this.verts[i+1]=0;
            this.verts[i+2]=0;
            this.colors[i]=0;
            this.colors[i+1]=0;
            this.colors[i+2]=0;
            this.alphas[t] = 0;
            this.sizes[t] = 0;
            this.rots[t]=0;
            this.splits[t]=0;
        }else{
            if(this.dynamicField)this.dynamicField(i,t);
            this.updatePosition(i,t);
            if(this.range){
                if(!this.range(this.verts[i],this.verts[i+1],this.verts[i+2])){
                    this.verts[i]=0;
                    this.verts[i+1]=0;
                    this.verts[i+2]=0;
                    this.colors[i]=0;
                    this.colors[i+1]=0;
                    this.colors[i+2]=0;
                    this.alphas[t] = 0;
                    this.sizes[t] = 0;
                    this.rots[t]=0;
                    this.splits[t]=0;
                    return;
                }
            }
            this.alphas[t]=Utils.clamp(this.alphas[t]+this.props.dAlpha.value,0, 1);
            this.sizes[t]=Utils.clamp(this.sizes[t]+this.props.dSize.value,0,10000);
            this.rots[t]=t%2===0?this.rots[t]+this.props.dRot.value:this.rots[t]-this.props.dRot.value;
            this.colors[i]=Utils.clamp(this.colors[i]+this.props.dColor.value.x,0,1);
            this.colors[i+1]=Utils.clamp(this.colors[i+1]+this.props.dColor.value.y,0,1);
            this.colors[i+2]=Utils.clamp(this.colors[i+2]+this.props.dColor.value.z,0,1);
        }
    }
    updatePosition(i,t){
        this.speeds[i]=this.speeds[i]+this.accelerates[i]+this.dynamics[i];
        this.speeds[i+1]=this.speeds[i+1]+this.accelerates[i+1]+this.dynamics[i+1];
        this.speeds[i+2]=this.speeds[i+2]+this.accelerates[i+2]+this.dynamics[i+2];
        let dx=this.speeds[i]*0.02;//this.dTime;//19.3.21
        let dy=this.speeds[i+1]*0.02;//this.dTime;//19.3.21
        let dz=this.speeds[i+2]*0.02;//this.dTime;//19.3.21

        this.verts[i]+=dx;
        this.verts[i+1]+=dy;
        this.verts[i+2]+=dz;
    }
    updateAttribute(){
        this.geometry.getAttribute('position').copyArray(this.verts);
        this.geometry.getAttribute('alpha').copyArray(this.alphas);
        this.geometry.getAttribute('size').copyArray(this.sizes);
        this.geometry.getAttribute('rot').copyArray(this.rots);
        this.geometry.getAttribute('split').copyArray(this.splits);
        this.geometry.getAttribute('color').copyArray(this.colors);
        this.geometry.getAttribute('position').needsUpdate=true;
        this.geometry.getAttribute('alpha').needsUpdate=true;
        this.geometry.getAttribute('size').needsUpdate=true;
        this.geometry.getAttribute('rot').needsUpdate=true;
        this.geometry.getAttribute('split').needsUpdate=true;
        this.geometry.getAttribute('color').needsUpdate=true;
    }
    initVariable(){
        this.isPoints=true;
        let c3=this.props.count.value*3;
        let c1=this.props.count.value;
        this.verts=new Float32Array(c3);
        this.lifes=new Float32Array(c1);
        this.alphas=new Float32Array(c1);
        this.sizes=new Float32Array(c1);
        this.speeds=new Float32Array(c3);
        this.accelerates=new Float32Array(c3);
        this.dynamics=new Float32Array(c3);
        this.starts=new Uint16Array(c1);
        this.rots=new Float32Array(c1);
        this.splits=new Uint8Array(c1);
        this.colors=new Float32Array(c3);
        this.requests=[0,0];
        this.state=0;
        this.oldTime=null;
        this.dTime=0;
        this.dFrame=0;
        this.tempCount=0;
        this.dynamicField=null;//()=>{};
        this.range=null;
    }
    initComponents(){
        this.initGeometry();
        this.initMaterial();
    }
    initGeometry(){
        this.geometry.setAttribute('position',new THREE.BufferAttribute(this.verts,3));
        this.geometry.setAttribute('alpha',new THREE.BufferAttribute(this.alphas,1));
        this.geometry.setAttribute('size',new THREE.BufferAttribute(this.sizes,1));
        this.geometry.setAttribute('rot',new THREE.BufferAttribute(this.rots,1));
        this.geometry.setAttribute('split',new THREE.BufferAttribute(this.splits,1));
        this.geometry.setAttribute('color',new THREE.BufferAttribute(this.colors,3));
    }
    initMaterial(){
        this.material.uniforms={
            map:{value:this.props.texture.value},
            splitCount:{value:this.props.splitCount.value},
            sizeAttenuation:{value:this.props.sizeAttenuation.value},
            scale:{value:this.props.scale.value}
        };
        this.material.vertexShader=this.vertexShader;
        this.material.fragmentShader=this.props.texture.value?this.fragmentShaderSampler:this.fragmentShaderColour;
        this.material.transparent=true;
        this.material.depthWrite=false;
        // this.material.blending=THREE.CustomBlending;
        // this.material.blendSrc=THREE.SrcAlphaFactor;
        // this.material.blendDst=THREE.DstAlphaFactor;
        // this.material.blendEquation=THREE.AddEquation;
        this.material.blending=THREE.AdditiveBlending;
    }
    dispose(){
        this.stop(true);
        this.geometry.dispose();
        if(this.material.uniforms.map)this.material.uniforms.map.value.dispose();
        this.material.dispose();
        this.verts=null;
        this.lifes=null;
        this.alphas=null;
        this.sizes=null;
        this.speeds=null;
        this.accelerates=null;
        this.dynamics=null;
        this.starts=null;
        this.rots=null;
        this.colors=null;
        this.splits=null;
        this.requests=null;
        this.state=null;
        this.oldTime=null;
        this.dTime=null;
        this.dFrame=null;
        this.tempCount=null;
        this.range=null;
        this.props.dispose();
    }
    get vertexShader(){
        return [
            'uniform float scale;' ,
            'attribute float alpha;' ,
            'attribute float size;' ,
            'attribute float rot;' ,
            'attribute float split;' ,
            'attribute vec3 color;',
            'uniform bool sizeAttenuation;' ,
            'varying float vAlpha;' ,
            'varying float vRot;' ,
            'varying float vSplit;' ,
            'varying vec3 vColor;' ,
            'void main(){' ,
            '   vec4 mvPosition=modelViewMatrix*vec4(position,1.);' ,
            '   vec4 pmvPosition=projectionMatrix*mvPosition;' ,
            '   gl_Position=pmvPosition;' ,
            '   vAlpha=alpha;' ,
            '   vRot=rot;' ,
            '   vSplit=split;' ,
            '   vColor=color;' ,
            '   gl_PointSize = size;' ,
            '   if(!sizeAttenuation){' ,
            '      bool isPerspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 );' ,
            '      if ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );' ,
            '   }' ,
            '}'
        ].join('\n');
    }
    get fragmentShaderColour(){
        return [
            'varying vec3 vColor;' ,
            'varying float vAlpha;' ,
            'void main(){' ,
            '   gl_FragColor=vec4(vColor,vAlpha);' ,
            '}'
        ].join('\n');
    }
    get fragmentShaderSampler(){
        return [
            'uniform sampler2D map;' ,
            'uniform int splitCount;' ,
            'varying vec3 vColor;' ,
            'varying float vAlpha;' ,
            'varying float vRot;' ,
            'varying float vSplit;' ,
            'vec2 rotate( float ang, vec2 v ){',
            '   float s = sin(ang);',
            '   float c = cos(ang);',
            '   return vec2( c*v.x-s*v.y, s*v.x+c*v.y );',
            '}',
            'vec2 split2(in vec2 uv,float count){' ,
            '   if(count==0.){' ,
            '      uv.x*=0.5;' ,
            '   }else{' ,
            '      uv.x=uv.x*0.5+0.5;' ,
            '   }' ,
            '   return uv;' ,
            '}',
            'vec2 split3(in vec2 uv,float count){' ,
            '   if(count==0.){' ,
            '      uv.x*=0.5;' ,
            '      uv.y=uv.y*0.5+0.5;' ,
            '   }else if(count==1.){' ,
            '      uv.x=uv.x*0.5+0.5;' ,
            '      uv.y=uv.y*0.5+0.5;' ,
            '   }else{' ,
            '      uv.y*=0.5;' ,
            '   }' ,
            '   return uv;' ,
            '}',
            'vec2 split4(in vec2 uv,float count){' ,
            '   if(count==0.){' ,
            '      uv.x*=0.5;' ,
            '      uv.y=uv.y*0.5+0.5;' ,
            '   }else if(count==1.){' ,
            '      uv.x=uv.x*0.5+0.5;' ,
            '      uv.y=uv.y*0.5+0.5;' ,
            '   }else if(count==2.){' ,
            '      uv.x=uv.x*0.5;' ,
            '      uv.y=uv.y*0.5;' ,
            '   }else{' ,
            '      uv.x=uv.x*0.5+0.5;' ,
            '      uv.y*=0.5;' ,
            '   }' ,
            '   return uv;' ,
            '}',
            'vec2 split5(in vec2 uv,float count){' ,
            '   if(count==0.){' ,
            '      uv.x*=0.333333;' ,
            '      uv.y=uv.y*0.5+0.5;' ,
            '   }else if(count==1.){' ,
            '      uv.x=uv.x*0.333333+0.333333;' ,
            '      uv.y=uv.y*0.5+0.5;' ,
            '   }else if(count==2.){' ,
            '      uv.x=uv.x*0.333333+0.666666;' ,
            '      uv.y=uv.y*0.5+0.5;' ,
            '   }else if(count==3.){' ,
            '      uv.x=uv.x*0.5;' ,
            '      uv.y=uv.y*0.5;' ,
            '   }else{' ,
            '      uv.x=uv.x*0.5+0.5;' ,
            '      uv.y*=0.5;' ,
            '   }' ,
            '   return uv;' ,
            '}',
            'vec2 split6(in vec2 uv,float count){' ,
            '   if(count==0.){' ,
            '      uv.x*=0.333333;' ,
            '      uv.y=uv.y*0.5+0.5;' ,
            '   }else if(count==1.){' ,
            '      uv.x=uv.x*0.333333+0.333333;' ,
            '      uv.y=uv.y*0.5+0.5;' ,
            '   }else if(count==2.){' ,
            '      uv.x=uv.x*0.333333+0.666666;' ,
            '      uv.y=uv.y*0.5+0.5;' ,
            '   }else if(count==3.){' ,
            '      uv.x=uv.x*0.333333;' ,
            '      uv.y=uv.y*0.5;' ,
            '   }else if(count==4.){' ,
            '      uv.x=uv.x*0.333333+0.333333;' ,
            '      uv.y=uv.y*0.5;' ,
            '   }else{' ,
            '      uv.x=uv.x*0.333333+0.666666;' ,
            '      uv.y*=0.5;' ,
            '   }' ,
            '   return uv;' ,
            '}',
            'void main(){' ,
            '   vec2 uv=vec2(gl_PointCoord.xy);' ,
            '   vec2 ruv=rotate(vRot,uv-0.5)+0.5;' ,
            '   if(splitCount==2){' ,
            '      ruv=split2(ruv,vSplit);' ,
            '   }else if(splitCount==3){' ,
            '      ruv=split3(ruv,vSplit);' ,
            '   }else if(splitCount==4){' ,
            '      ruv=split4(ruv,vSplit);' ,
            '   }else if(splitCount==5){' ,
            '      ruv=split5(ruv,vSplit);' ,
            '   }else if(splitCount==6){' ,
            '      ruv=split6(ruv,vSplit);' ,
            '   }' ,
            '   vec4 col=texture2D(map,ruv);' ,
            '   gl_FragColor=vec4(col.rgb*vColor,col.a*vAlpha);' ,
            '}'
        ].join('\n');
    }
}